Ruby on Rails: Create a Select Drop Down from an Array

Problem: You wish to populate a “collection_select” box with an Array.

I recently had an issue where I wanted to create a select box populated from an array of continuous years. I wanted to use thecollection_select method so it would have a minimal “code footprint” and play nice with the form that I had built. The application that I’m working on is in Rails2 and I didn’t want to divert heavily or make to many changes, and after some fiddling I came up with a nice solution: In the environment.rb file I created an Array constant to contain the year range that I wanted to use:

#Constant Values
YEARS_ARRAY = Array.new(89) {|i| 1920+i}

This code is a shortcut to create an array of values between 1920 and 2008 (88 years). It creates a new array with a size of 88, and then uses the i value to populate each year incrementally. I was pleased to find a method like this!

The form declaration contained the object that I wanted to populate with the form values using the Rails2 syntax:

<table class=”new”>
<% form_for(@movie_poster) do |f| %>
 <tr>
<th>Title:</th>
  <td><%= f.text_field :title %></td>
 </tr>
…..
<% end %>
</table>

*note the newer Rails2 syntax for the form…

I will populate my select box with an Array of Objects. I need to create my Object first to populate my Labels and Values, and I used an “old trick” from Struts by creating a Ruby LabelValue object that mimics Struts LabelValueBean utility POJO. The object has a label and value accessor, which works extremely well for any set of arrays since any kind of mapping scheme would consume duplicate keys and often select boxes, radio buttons, etc may need some duplication. Here is my label_value.rb class, located in my model directory:

# Creates a Label and value for select boxes and
# forms without clearly delinieated# objects, such as Arrays.
class LabelValue
# name the accessors. Label and Value
attr_accessor :label, :value
end

Now that I have a model to work with and an Array Constant to use, I created the “helper” to use with the form tag in the application_helper.rb file, so I might use it throughout the application’s forms. There are two methods, one public, and one private. Note the “Select year” prompt:

# selection for a year value
# ‘f’ represents the passed in form value
def year_select(f)
f.collection_select(:year,year_lookup,:label,:value,{:prompt=>”Select year”})
end
# ———— PRIVATE METHODS —————–

private

def year_lookup
#create an emptycollection to hold the LabelValue Objects
years = Array.new()#populate the ArrayYEARS_ARRAY.each do |yr| y = LabelValue.new
y.label = yr
y.value = yr
years.push(y)
end
years
end

Finally, we need to call the _helper method from the form:

<tr>
<th>Grade:</th>
<td><%= grade_select(f) %></td>
</tr>

The resulting code renders a select box:

<select id=”movie_poster_year” name=”movie_poster[year]”>
<option value=””>Select year</option>
<option value=”1920″>1920</option>
<option value=”1921″>1921</option>
<option value=”1922″>1922</option>
<option value=”1923″>1923</option>
<option value=”1924″>1924</option>

# continues…

<option value=”2005″>2005</option>
<option value=”2006″>2006</option>
<option value=”2007″>2007</option>
<option value=”2007″>2007</option>
</select>

The nice thing is reusability of the LabelValue class, the YEAR Constant and the collection_select itelf. Everything can be accessed as it is needed, whether you want to use the Constant Array for something else, the LabelValue object for a different array or tuple, or the actual rendered select box in different views.

UPDATE, 1/15:

See the comments below for a nice alternative and discussion!

10 thoughts on “Ruby on Rails: Create a Select Drop Down from an Array

  1. Looks nice, Danilo. You might, however, want to look at Structs in Ruby. You can replace your class definition with the following line:

    LabelValue = Struct.new(:label, :value)

    This will create a new class called LabelValue with accessors for label and value. Plus, it creates a constructor, so you can create instances like this:

    obj = LabelValue.new(‘mylabel’, ‘myvalue’)

    Also, you may wish to look at Ruby ranges to do the year array. Your constant could also be created like this:

    YEARS_ARRAY = (1920..2008).entries

    By the way, your year_lookup re-builds the select collection array every time it is called. You might want to put it in a constant (or class variable). Here is how I would do it:

    YEARS_SELECT_COLLECTION = YEARS_ARRAY.collect {|yr| LabelValue.new(yr,yr)}

    Happy New Year.
    -Tc

  2. Thanks for the comment. I “thought” there was a way of creating the array this way, but couldn’t find that particular constructor in any documentation ( hey, it’s “open source”, so…) I’ll definitely do that.

    I looked hard at using the “Struct” instead of the accessors, and still might do this. I had a worry that I might want to do “more” with the class at a later point, but the nice thing about Ruby is that your classes can be quite mutable without affecting their future functionality.

    As far as putting the collection into a constant, you’re absolutely right. I just missed that and it would definitely be an enhancement!

    Always so much to learn. Nice comment!

  3. Tom. You are the man. Here is my the pertinent code in my “environment.rb” file. I just deleted the LabelValue class entirely. It very much reminds me of the bean declarations in the Application Context, no?

    # ——- Structs
    LabelValue = Struct.new(:label,:value)

    # ——- Constant Values

    # Year array
    YEARS_ARRAY = (1920..2008).entries

    # Year collection
    YEARS_SELECT_COLLECTION = YEARS_ARRAY.collect {|yr| LabelValue.new(yr,yr)}

    ….

    From there, I just use the method in the helper file to populate the values — it’s all “static” so you get the single instance. Much, much cleaner.

    Thanks.

  4. Thanks Tom & Danilo, this was useful stuff as I’m trying to generate a similar thing (as I’m sure anyone who’s trying to use rails for anything useful has)

    Just wondering if it would make more sense to auto-increment the max year so you don’t have to fix it with a New Years hangover once a year?

    YEARS_ARRAY = (1920..Time.new.strftime(‘%Y’).to_i).entries

    Cheers
    Ben

  5. Please give me your full code on below, helper method and how u called it from view. And why keeping the below in environment.rb

    You are the man. Here is my the pertinent code in my “environment.rb” file. I just deleted the LabelValue class entirely. It very much reminds me of the bean declarations in the Application Context, no?

    # ——- Structs
    LabelValue = Struct.new(:label,:value)

    # ——- Constant Values

    # Year array
    YEARS_ARRAY = (1920..2008).entries

    # Year collection
    YEARS_SELECT_COLLECTION = YEARS_ARRAY.collect {|yr| LabelValue.new(yr,yr)}

    ….

    From there, I just use the method in the helper file to populate the values — it’s all “static” so you get the single instance. Much, much cleaner.

    Thanks

  6. thanks for this guys,

    nice solution, any improvements since ?

    My form field entry is now:

    My application wide helper is :

    def channel_select(f)
    f.collection_select(:source, CHANNEL_SELECT_COLLECTION, :label, :value, {:prompt => ‘Select Channel Source’})
    end

    And my code in environment.rb is

    LabelValue = Struct.new(:label, :value)
    CHANNEL_SOURCES_ARRAY = [‘abc’, ‘def’, ‘ghi’]
    CHANNEL_SELECT_COLLECTION = CHANNEL_SOURCES_ARRAY.collect { |chan| LabelValue.new(chan,chan) }

    I am cautiosly wary about editing such code into environment.rb, might move the array content out specific env files, or just put something into /lib

    any thoughts ?

  7. Probably depends upon how much stuff you’re putting in the environment.rb file. I see why you might want to do this if you have a lot of label/values to manage. I kind of like doing this over putting them into a database, as long as they are static in nature and read-only.

    The key is striking the balance, and you’ll know for sure if you’ve done this if it’s maintainable.

  8. Rails 2.1+
    You may want to take a look at using the Rails.cache capabilities.

    In a class:
    app/models/years.rb
    class Years
    def self.get_years
    Rails.cache.fetch(‘years’) {(1920..Time.now.strftime(’%Y’).to_i).entries}
    end

    def self.update_years
    if self.get_years.max < Time.now.strftime(’%Y’).to_i
    Rails.cache.delete(‘years’)
    self.get_years
    end
    end
    end

    use Years.get_years to return a cached version of the years.
    use Years.update_years to update the cache with the current year dataset…might want to schedule updates for every night at 12:00AM.
    Note that if you are running mongrel each cache is seen as a separate store unless you go with memcached…
    You can get crazy with caching using memcached if you have a data set that you really want to have highly available/distributed.

  9. Sorry but I can’t understand what is so nice in Ruby!? All that code to populate/create select dropdown!? And now PHP haters will say that Ruby is the best web programming language. I was in Perl, PHP and Ruby, but after some “programming excursion” for me the only one would be “old” and “ugly” PHP. Best regards and good luck with programming in ruby.

  10. I tend not to beat up on other people’s languages — it’s like calling your kids ugly. They all have their plusses and minuses. One of the big ones with Ruby is that it’s damned fun to write code in. Frankly, I don’t write Ruby much anymore (I’m more of a Python/Django guy, really), but I know a lot of people that really like it.

    PHP is a wonderful, powerful and fast language. It has particularly powerful uses, but I’m not so fond of it on the server-side, where Java, Python and even .Net may be better choices. It all depends on how you want to implement your design patterns, how “big” your project is, the standards of your corporate architecture (If you’re programming for a small business or out of your garage, language choices matter a whole lot less than if you company has 240,000 employees and thousands of deployed systems).

    Also, if you notice, this code is years old. I haven’t looked into the latest Ruby deployment, but I would imagine that time has moved on here.

Leave a comment